package com.hzw.saas.common.syslog.aspect;

import java.util.*;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.hzw.saas.api.syslog.ISysLogService;
import com.hzw.saas.api.syslog.model.SysLog;
import com.hzw.saas.common.security.utils.SecurityUtils;
import com.hzw.saas.common.syslog.config.SysLogLevelConfig;
import com.hzw.saas.common.util.IPHelper;
import com.hzw.saas.common.util.enums.LogLevelEnum;
import cn.hutool.core.date.SystemClock;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.json.JSONUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Aspect
@Component
@RequiredArgsConstructor
@Order(1)
@Slf4j
public class SysLogAspect {
    private final static int PARAMS_MAS_LENGTH = 5000;

    private final SysLogLevelConfig levelConfig;

    @Resource(name = "sysLogServiceImpl")
    private ISysLogService sysLogService;

    private LogLevelEnum baseLevel;

    @Autowired(required = false)
    private HttpServletRequest request;

    @PostConstruct
    private void postConstruct() {
        this.baseLevel = LogLevelEnum.instance(levelConfig.getLevel());
    }

    @Around("@annotation(sysLog)")
    public Object around(ProceedingJoinPoint joinPoint, com.hzw.saas.common.config.annotation.SysLog sysLog) throws Throwable {
        boolean enableLog = LogLevelEnum.enableLog(this.baseLevel, LogLevelEnum.instance(sysLog.logLevel()));
        boolean enableLog2File = enableLog && (sysLog.mode() == 1 || sysLog.mode() == 3);
        boolean enableLog2DB = enableLog && (sysLog.mode() == 2 || sysLog.mode() == 3);

        String username;
        try {
            username = SecurityUtils.getUser().getUsername();
        } catch (Exception e) {
            username = "";
        }
        String httpMethod = "";
        String uri = "";
        String ip = "";
        try {
            httpMethod = request != null ? request.getMethod() : sysLog.httpMethod();
            uri = request != null ? request.getRequestURI() : sysLog.uri();
            ip = IPHelper.getIpAddr();
        } catch (Exception e) {
            // ignore~
        }

        //请求的参数
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        List<String> paramNameList = Arrays.asList(methodSignature.getParameterNames());
        List<Object> paramList = Arrays.asList(joinPoint.getArgs());
        Map<String, Object> map = new HashMap<>();
        for (int i = 0; i < paramNameList.size(); i++) {
            if (paramList.get(i) instanceof HttpServletRequest ||
                paramList.get(i) instanceof HttpServletResponse ||
                paramList.get(i) instanceof MultipartFile)
                continue;
            map.put(paramNameList.get(i), paramList.get(i));
        }
        String params = JSONUtil.toJsonStr(map);
        if (params == null) {
            params = "";
        }
        if (params.length() > PARAMS_MAS_LENGTH) {
            params = params.substring(0, PARAMS_MAS_LENGTH);
        }

        //文件日志
        if (enableLog2File) {
            log.info("request start: {}({}) -> {} {}, params: {}", username, ip, httpMethod, uri, params);
        }

        // 执行方法
        long beginTime = SystemClock.now();
        Exception exception = null;
        Object result = null;
        try {
            //执行方法
            result = joinPoint.proceed();
        } catch (Exception e) {
            exception = e;
        }
        // 执行时长(毫秒)
        long time = SystemClock.now() - beginTime;

        //文件日志
        if (enableLog2File) {
            String logInfo = StrFormatter.format("request over: {}({}) => {} {}, finish in {}ms", username, ip, httpMethod, uri, time);
            if (Objects.nonNull(exception)) {
                log.warn(logInfo.concat(", with exception: ") + exception.getMessage());
            } else {
                if (Objects.equals(sysLog.printResult(), 1)) {
                    logInfo = logInfo.concat(", with result: ") + JSONUtil.toJsonStr(result);
                }
                log.info(logInfo);
            }
        }

        // 记录数据库日志
        if (enableLog2DB) {
            try {
                SysLog sysLogEntity = new SysLog();
                //注解
                sysLogEntity.setOperation(sysLog.operation());
                sysLogEntity.setUri(uri);
                sysLogEntity.setHttpMethod(httpMethod);
                //用户名
                sysLogEntity.setUsername(username);
                //请求的方法名
                String className = joinPoint.getTarget().getClass().getName();
                String methodName = joinPoint.getSignature().getName();
                sysLogEntity.setClassMethod(className + "." + methodName + "()");
                //请求参数
                sysLogEntity.setParams(params);
                //执行时间
                sysLogEntity.setTime(time);
                //可能的异常
                if (exception != null)
                    sysLogEntity.setException(exception.getMessage());
                //设置IP地址
                sysLogEntity.setIp(IPHelper.getIpAddr());
                //log time
                sysLogEntity.setCreateDate(new Date());
                //保存系统日志
                // TODO: ZZL@07/19/2021 增加异步执行
                sysLogService.save(sysLogEntity);
            } catch (Exception e) {
                log.error("sys log error", e);
            }
        }

        // 返回结果
        if (exception != null) {
            throw exception;
        } else {
            return result;
        }
    }

}
